#include <QFont>
#include <QString>
#include "MeshRenderer.h"
#include <cmath>

using namespace std;

//==============================================================================
// OpenGL Initialization
//==============================================================================

/**	Sets up the OpenGL rendering environment.
 */
void QGLSurfacesRenderer::initializeGL()
{
	glHint( GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST );
	glClearColor( 0,0,0,0 ); 	// Let OpenGL clear to black

	GLfloat specularity[4] = {1.0f,1.0f,1.0f,1.0f};

	// Set up OpenGL State
	glHint(GL_POLYGON_SMOOTH, GL_NICEST);
	glEnable(GL_POINT_SMOOTH);
	glEnable(GL_POLYGON_SMOOTH);
	glEnable(GL_LINE_SMOOTH);
	glEnable(GL_NORMALIZE);
	glMaterialf(GL_FRONT, GL_SPECULAR, specularity[0]);

	// Lighting
	glLoadIdentity();
	setDefaultLight();

	glPointSize(7);
	glLineWidth(5);
}


//==============================================================================
// OpenGL Lighting
//==============================================================================

/**	Sets the default lighting for the scene.
 */
void QGLSurfacesRenderer::setDefaultLight(void)
{
	GLfloat pos1[] = { 0.0, 0.0, 1.0, 0.0};
	GLfloat pos2[] = { 0.0, 0.0, 0.0, 0.0};
	GLfloat pos3[] = { 1.0, 0.0, 0.0, 0.0};
	GLfloat pos4[] = { -1.0, 0.0, 0.0, 0.0};
	GLfloat dim[] = { 0.1,  0.1,  0.1, 0.0};

	glEnable(GL_LIGHTING);
	glEnable(GL_LIGHT0);    
	glLightfv(GL_LIGHT0,GL_POSITION, pos1);
	glLightfv(GL_LIGHT0,GL_DIFFUSE, dim);

	glEnable(GL_LIGHT1);    
	glLightfv(GL_LIGHT1,GL_POSITION, pos2);
	glLightfv(GL_LIGHT1,GL_DIFFUSE, dim);

	glEnable(GL_LIGHT2);    
	glLightfv(GL_LIGHT2,GL_POSITION, pos3);
	glLightfv(GL_LIGHT2,GL_DIFFUSE, dim);

	glEnable(GL_LIGHT3);    
	glLightfv(GL_LIGHT3,GL_POSITION, pos4);
	glLightfv(GL_LIGHT3,GL_DIFFUSE, dim);
}


/** Dims the scene by disabling bright lightes
 *  and enabling dim ones.
 */
void QGLSurfacesRenderer::dimLights()
{
	glPushMatrix();
	glLoadIdentity();

	setDefaultLight();

	glPopMatrix();
}


/** Brightens scene by enabling four bright lights
 */
void QGLSurfacesRenderer::brightenLights()
{
	GLfloat pos1[] = { 0, 0.0, 1.0, 0.0};
	GLfloat pos2[] = { 0, 0.0, 0.0, 0.0};
	GLfloat brighter[] = { 1.0,  1.0,  1.0,  1.0};

	glPushMatrix();
	glLoadIdentity();

	glEnable(GL_LIGHTING);
	glEnable(GL_LIGHT0);    
	glLightfv(GL_LIGHT0,GL_POSITION, pos1);
	glLightfv(GL_LIGHT0,GL_DIFFUSE, brighter);

	glEnable(GL_LIGHT1);    
	glLightfv(GL_LIGHT1,GL_POSITION, pos2);
	glLightfv(GL_LIGHT1,GL_DIFFUSE, brighter);

	glPopMatrix();
}

//==============================================================================
// OpenGL Rendering
//==============================================================================

/**
 *	Refreshes the screen and redraws the scene, including
 *	the mesh and any supplemental items.
 */
void QGLSurfacesRenderer::paintGL()
{
	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);  

	//check if model is loaded
	if( modelFrame )
	{
		drawFrame( );
		drawHoverSelection();
	}
}


/**
 *	Renders the mesh and the important (highlighted) areas.
 */
void QGLSurfacesRenderer::drawFrame( )
{
	if( !modelFrame->coarse->n_vertices())
		return;

	glMatrixMode( GL_PROJECTION );
	glLoadMatrixd( modelFrame->t.projectionMatrix );
	glMatrixMode( GL_MODELVIEW );
	glLoadMatrixd( modelFrame->t.modelviewMatrix );

	if (!zoomComplete)
		zoomComplete = true;

	switch( renderMode )
	{
		case RENDER_SMOOTH:
			renderSmoothMode();
			break;

		case RENDER_FLAT:
			renderFlatMode();
			break;

		case RENDER_TOOL:
			renderToolMode();
			break;

		default:
			break;
	}
	// render the important part if it exists
	if( (modelFrame->getImportanceMesh()) && 
		            (modelFrame->getImportanceMesh()->n_faces() > 0) )
	{
		glDisable( GL_DEPTH_TEST );
		brightenLights();
		drawMesh( modelFrame->getImportanceMesh() );
		drawWeights(modelFrame->getImportanceMesh());
	}

	if( renderMode != RENDER_TOOL )
	{
		brightenLights();
		drawSharpEdges( modelFrame->active );
	}
}


void QGLSurfacesRenderer::renderToolMode( )
{	
	bool hasImportance;

	// let there be light(ing)
	glEnable(GL_LIGHTING);

	// see if importance is set for the mesh
	hasImportance = (modelFrame->getImportanceMesh()) && 
		            (modelFrame->getImportanceMesh()->n_faces() > 0);
	
	// make brighter if no importnace
	if (!hasImportance)
		brightenLights();
	else
		dimLights();

	// render the unimportant part of the mesh
	glEnable(GL_DEPTH_TEST);
	drawMesh( modelFrame->active ); 
	glDisable(GL_DEPTH_TEST);
}


/**
 *	Renders the weight numbers for the vertices of the
 *	specified mesh.
 */
void QGLSurfacesRenderer::drawWeights(PolyMesh* mesh)
{
	const unsigned x = 0, y = 1, z = 2;		// constants (x, y, z) for Vec3d
	const double   NUDGE = 5;				// for nudging the text a bit

	QString	vertexText;		// the text to draw
	Vec3d	textPosition;	// where to draw the text
	CVITER	vIterator;		// iterator for vertices
	VData	vd;

	vIterator = mesh->vertices_begin();				// put iterator at first vertex
	do												// loop through all vertices
	{
		vd = mesh->data( vIterator.handle() );
		if( vd.weight() > 0 )
		{
			// vertex weight is the text to draw
			vertexText = QString(weightToFraction(double( vd.weight() )).c_str()); 
			
			textPosition = mesh->point(vIterator);		// get vertex in object coordinates
			projectPoint(textPosition);					// project into window coordinates

			qglColor(QColor(255, 255, 255, 255));		// draw using white color
			renderText( textPosition[x] + NUDGE,		// render text at this x 
						textPosition[y] - NUDGE,		// and this y (window coords)
						vertexText,						// this text to be rendered
						QFont("Arial"));				// using this font
		}
	}
	while ( ++vIterator != mesh->vertices_end() );	// stop if run out of vertices
}


void QGLSurfacesRenderer::drawSharpEdges( PolyMesh* mesh )
{
	PolyMesh::EdgeIter eit( mesh->edges_begin() ), eend( mesh->edges_end() );
	PolyMesh::VertexHandle left, right;
	PolyMesh::HalfedgeHandle heh;
	PolyMesh::EdgeData ed;

	glEnable(GL_COLOR_MATERIAL);
	glShadeModel( GL_SMOOTH );
	glEnable( GL_CULL_FACE );

	Vec4f sharpEdgeColor = Vec4f( 0.75, 0, 0.2, 1 );
	glColor4fv( &(sharpEdgeColor[0]) );

	glBegin( GL_LINES );
	for( ; eit != eend; ++eit )
	{
		ed = mesh->data( eit.handle() );
		if( ed.getColor() == sharpEdgeColor )
		{
			heh = mesh->halfedge_handle( eit.handle(), 0 );
			left = mesh->from_vertex_handle( heh );
			right =  mesh->to_vertex_handle( heh );
			glNormal3dv( &(mesh->normal(left)[0]) );
			glVertex3dv( &(mesh->point(left)[0]) );
			glNormal3dv( &(mesh->normal(right)[0]) );
			glVertex3dv( &(mesh->point(right)[0]) );
		}
	}
	glEnd();
	glDisable( GL_COLOR_MATERIAL );
}


void QGLSurfacesRenderer::renderSmoothMode( )
{
	PolyMesh* mesh = modelFrame->active;
	mesh->request_vertex_normals();
	mesh->request_face_colors();
	mesh->update_normals();
	PolyMesh::ConstFaceIter fIt(mesh->faces_begin()), fEnd(mesh->faces_end());
	PolyMesh::ConstFaceVertexIter fvIt;

	// Set up the required GL variables
	glDisable(GL_CULL_FACE);
	glDisable(GL_BLEND);
	glEnable(GL_COLOR_MATERIAL);
	glEnable(GL_DEPTH_TEST);
	glShadeModel(GL_SMOOTH);
	glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);

	// Set up lighting and color
	//brightenLights();
	glColor4f( 0.0, 0.41, 0.8, 1.0 );

	// Render all the faces of the mesh
	for( ; fIt !=fEnd; ++fIt )
	{
		glBegin( GL_POLYGON );
			
			fvIt = mesh->cfv_iter(fIt.handle());
			glNormal3dv( &(mesh->normal(fIt.handle())[0]) );
			for( ; fvIt != false; ++fvIt )
			{
				glNormal3dv( &(mesh->normal(fvIt)[0]) );
				glVertex3dv( &(mesh->point(fvIt)[0]) );
			}
		
		glEnd( );
	}

	// Disable what was enabled earlier
	glDisable( GL_DEPTH_TEST );
	glDisable( GL_COLOR_MATERIAL );
}



void QGLSurfacesRenderer::renderFlatMode( )
{
	PolyMesh* mesh = modelFrame->active;
	PolyMesh::ConstFaceIter fIt(mesh->faces_begin()), fEnd(mesh->faces_end());
	PolyMesh::ConstFaceVertexIter fvIt;

	mesh->request_vertex_normals();
	mesh->request_face_colors();
	mesh->update_normals();

	// Set up the required GL variables
	glDisable(GL_CULL_FACE);
	glDisable(GL_BLEND);
	glEnable(GL_COLOR_MATERIAL);
	glEnable(GL_DEPTH_TEST);
	glShadeModel( GL_FLAT );
	glPolygonMode( GL_FRONT_AND_BACK, GL_FILL );

	// Set up lighting and color
	//brightenLights();
	glColor4f( 0.0, 0.41, 0.8, 1.0 );
	
	// Render all the faces of the mesh
	for( ; fIt !=fEnd; ++fIt )
	{
		glBegin( GL_POLYGON );
			glNormal3dv( &(mesh->normal(fIt)[0]) );
			fvIt = mesh->cfv_iter(fIt.handle());
			for( ; fvIt != false; ++fvIt )
				glVertex3dv( &(mesh->point(fvIt)[0]) );
		
		glEnd( );
	}

	// Disabled what was enabled earlier
	glDisable( GL_DEPTH_TEST );
	glDisable( GL_COLOR_MATERIAL );
}


void QGLSurfacesRenderer::drawMesh( PolyMesh* mesh )
{
	GLfloat	oldPointSize;
	Vec4f	mixedColor;
	Vec3d	pointPosition;
	Vec3d	vertexNormal;
	Vec4f	evenVertexColor;

	mesh->request_vertex_normals();
	mesh->request_face_colors();
	mesh->update_normals();

	PolyMesh::ConstVertexIter vIterator;
	PolyMesh::ConstFaceIter fIt(mesh->faces_begin()), fEnd(mesh->faces_end());
	PolyMesh::ConstFaceVertexIter fvIt;

	float grey[] = { 0.23, 0.26, 0.30, 0.7 };
	float blue[] = { 0.0, 0.41, 1.0, 0.6 };
	Vec4f connectedVertexColor = Vec4f( 0.85, 0.87, 0.25, 1 );
	
	//Enable a few things before drawing
	glEnable(GL_COLOR_MATERIAL);			
	glEnable(GL_CULL_FACE);	

	//Render shaded polygons with blending
	glShadeModel( GL_SMOOTH );		
	glEnable(GL_BLEND);				
	glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);		
	glColor4fv(&(grey[0]));			
	glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
	for( fIt=mesh->faces_begin(); fIt !=fEnd; ++fIt )
	{
		glBegin( GL_POLYGON );
		fvIt = mesh->cfv_iter(fIt.handle());
		glNormal3dv( &(mesh->normal(fIt.handle())[0]) );
		for( ; fvIt != false; ++fvIt )
		{
			glNormal3dv( &(mesh->normal(fvIt)[0]) );
			glVertex3dv( &(mesh->point(fvIt)[0]) );
		}
		glEnd( );
	}

	//Draw wireframe lines with blending
	PolyMesh::EdgeIter eit( mesh->edges_begin() ), eitEnd( mesh->edges_end() );
	PolyMesh::VertexHandle left, right;
	PolyMesh::HalfedgeHandle heh;
	PolyMesh::EdgeData ed;
	Vec4f color;

	glPolygonMode( GL_FRONT_AND_BACK, GL_LINE );
	glBegin( GL_LINES );
	for( ; eit != eitEnd; ++eit )
	{
		heh = mesh->halfedge_handle( eit.handle(), 0 );
		left = mesh->from_vertex_handle( heh );
		right =  mesh->to_vertex_handle( heh );
		ed = mesh->data( eit.handle() );
		
		mixedColor = connectedVertexColor*(1 - interpVal) + Vec4f(blue)*interpVal;
		mixedColor[3] = min(interpVal+this->reconMeshOpacity, 0.8);
		color = (ed.getColor() == connectedVertexColor)? mixedColor : ed.getColor();
		
		glColor4fv( &(color[0]) );

		if (ed.getColor() == connectedVertexColor && mixedColor[3] == 0.0) 
		{
			continue;
		}

		glNormal3dv( &(mesh->normal(left)[0]) );
		glVertex3dv( &(mesh->point(left)[0]) );
		glNormal3dv( &(mesh->normal(right)[0]) );
		glVertex3dv( &(mesh->point(right)[0]) );
	}
	glEnd( );

	//Render the vertices without blending				
	glGetFloatv( GL_POINT_SIZE, &oldPointSize);			
	glPointSize(8.0);						
	glBegin(GL_POINTS);						
	vIterator = mesh->vertices_begin();		
	do										
	{
		if( mesh->color(vIterator) == connectedVertexColor )
		{
			mixedColor = mesh->color(vIterator)*(1 - interpVal) + Vec4f(blue)*interpVal;
			mixedColor[3] = min(interpVal + this->reconMeshOpacity, 0.8);
			if (mixedColor[3] == 0.0)	// if vertex is not visible
			{
				continue;				// don't bother showing it
			}
		}
		else
		{
			mixedColor = mesh->color(vIterator);
		}

		// calculate the final point position
		vertexNormal = mesh->normal(vIterator.handle());
		pointPosition = mesh->point(vIterator);

		// draw the vertex
		glColor4fv(&mixedColor[0]);
		glNormal3dv(&vertexNormal[0]);
		glVertex3dv(&pointPosition[0]);
	}
	while (++vIterator != mesh->vertices_end());
	glEnd();
	glPointSize(oldPointSize);

	//Disable the stuff we enabled before
	glDisable(GL_CULL_FACE);	
	glDisable(GL_COLOR_MATERIAL);
}


/**
 *	Renders a ghostly outline of the specified mesh.
 */
void QGLSurfacesRenderer::drawGhostMesh(PolyMesh* mesh)
{
	const unsigned x = 0, y = 1, z = 2;

	GLint			oldShadeModel;
	GLfloat			oldLineWidth;
	Vec3d			vertexCoords;
	VertexHandle	linePoint1;
	VertexHandle	linePoint2;

	PolyMesh::ConstFaceIter				fIterator;
	PolyMesh::ConstFaceVertexIter		fvIterator;
	PolyMesh::ConstFaceHalfedgeIter     heIterator;

	// remembder the old shade model
	glGetIntegerv(GL_SHADE_MODEL, &oldShadeModel);

	// set up rendering properties
	glShadeModel(GL_SMOOTH);
	glEnable(GL_COLOR_MATERIAL);
	glEnable(GL_CULL_FACE);
	glEnable(GL_BLEND);		
	glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA );

	// store old line width and set new one
	glGetFloatv(GL_LINE_WIDTH, &oldLineWidth);
	glLineWidth(2);

	// start drawing the mesh
	fIterator = mesh->faces_begin();
	do
	{
		heIterator = mesh->cfh_iter(fIterator);
		do
		{
			glPolygonMode( GL_FRONT_AND_BACK, GL_LINE );
			glBegin( GL_LINES );

				glColor4f(1, 1, 1, 0.35);

				linePoint1 = mesh->from_vertex_handle(heIterator);
				linePoint2 = mesh->to_vertex_handle(heIterator);
				
				glNormal3dv(&Vec3d(mesh->calc_vertex_normal(linePoint1))[x]);
				glVertex3dv(&(mesh->point(linePoint1))[x]);

				glNormal3dv(&Vec3d(mesh->calc_vertex_normal(linePoint2))[x]);
				glVertex3dv(&(mesh->point(linePoint2))[x]);

			glEnd();
		}
		while (++heIterator == true);
	}
	while (++fIterator != mesh->faces_end());

	// reset the stuff we set earlier
	glLineWidth(oldLineWidth);
	glDisable(GL_BLEND);
	glDisable(GL_CULL_FACE);
	glDisable(GL_COLOR_MATERIAL);
	glShadeModel(oldShadeModel);
}


/**
 *	Renders the hover-selected area of the mesh.
 */
void QGLSurfacesRenderer::drawHoverSelection()
{
	OutputDebugString(L"START QGLSurfacesRenderer::drawSelection\n");
	const unsigned x = 0, y = 1, z = 2; // Vec3d components

	GLfloat			oldPointSize;
	PolyMesh*		sourceMesh;
	Vec3d			vertexCoords;
	VertexHandle	selectedVertex;
	HalfedgeHandle	selectedEdge;
	FaceHandle		selectedFace;
	Vec3d			lineCoords[2];
	
	PolyMesh::Normal				faceNormal;
	PolyMesh::ConstFaceVertexIter	fvIterator;

	if (this->selectionHover == NULL)
	{
		return;
	}

	sourceMesh = this->modelFrame->getSelectableMesh();
	switch (this->selectionHover->type)
	{
		case VERTEX:
		{
			selectedVertex = this->selectionHover->selectedVertex;
			if (sourceMesh->is_valid_handle(selectedVertex))
			{
				vertexCoords	= sourceMesh->point(selectedVertex);

				glGetFloatv(GL_POINT_SIZE, &oldPointSize);
				glPointSize(12.0);
				glDisable(GL_LIGHTING);
				glColor3f(0,1,0);
				glBegin(GL_POINTS);
					glVertex3dv(&vertexCoords[x]);
				glEnd();
				glEnable(GL_LIGHTING);
				glPointSize(oldPointSize);
			}
			break;
		}
		case EDGE:
		{
			if (sourceMesh->is_valid_handle(this->selectionHover->selectedEdge))
			{
				selectedEdge = sourceMesh->halfedge_handle(this->selectionHover->selectedEdge, 0);
				
				glDisable(GL_LIGHTING);
				glColor3f(0,1,0);
				glLineWidth(3);
				glBegin(GL_LINES);
					selectedVertex	= sourceMesh->from_vertex_handle(selectedEdge);
					glVertex3dv(&sourceMesh->point(selectedVertex)[x]);
					selectedVertex	= sourceMesh->to_vertex_handle(selectedEdge);
					glVertex3dv(&sourceMesh->point(selectedVertex)[x]);
				glEnd();
				glEnable(GL_LIGHTING);
			}
			break;
		}
		case FACE:
		{
			selectedFace = this->selectionHover->selectedFace;
			if (sourceMesh->is_valid_handle(selectedFace))
			{
				faceNormal = sourceMesh->calc_face_normal(selectedFace);
				fvIterator = sourceMesh->cfv_iter(this->selectionHover->selectedFace);

				glDisable(GL_LIGHTING);
				glEnable(GL_BLEND);
				glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
				glColor4f(0, 1, 0, 0.2);
				glPolygonMode( GL_FRONT, GL_FILL );
				glBegin(GL_POLYGON);
				do
				{
					vertexCoords = sourceMesh->point(fvIterator);
					vertexCoords += (faceNormal * 0.0005);
					glVertex3dv(&vertexCoords[x]);
				}
				while (++fvIterator == true);
				glEnd();
				glDisable(GL_BLEND);
				glEnable(GL_LIGHTING);
				break;
			}
		}
	}

	OutputDebugString(L"END QGLSurfacesRenderer::drawSelection\n");
}

//==============================================================================
// Selection 
//==============================================================================

/**	Finds whatever is selected under the given window point and emits
 *	the signal that a selection was made.
 */
void QGLSurfacesRenderer::doSelection(const Vec3d& windowPoint)
{
	if (this->selectionHover == NULL)
	{
		return;
	}

	// finalize the hover-selection
	this->selection			= this->selectionHover;
	this->selectionHover	= NULL;

	//notify listeners of selection
	emit objectSelected(this->selection);
}

/**
 *	Finds whatever is selected under the given window point and stores
 *	the selection as a class attribute.
 */
void QGLSurfacesRenderer::doHoverSelection(const Vec3d& windowPoint)
{
	const double selectDelay = SELECT_DELAY_SEC;

	Vec3d		rayOrigin;
	Vec3d		rayDirection;
	PolyMesh*	source;
	Selection&	selection = *(new Selection());
	
	// block this code if it's happening too often
	if (clock() < this->nextSelectionTime)
	{
		return;
	}
	this->nextSelectionTime = clock() + selectDelay * CLOCKS_PER_SEC;

	// a few preliminary initializations
	unprojectRay(windowPoint, rayOrigin, rayDirection);
	source = modelFrame->getSelectableMesh();

	if (source == NULL)
	{
		return;
	}

	// initialize the selection structure
	selection.selectionSource = modelFrame;

	switch (selectionMode)
	{
		case VERTEX:
		{
			//check if vertex selected
			selection.type = VERTEX;
			selection.selectedVertex = getVertexSelection(*source, rayOrigin, rayDirection);
			break;
		}
		case EDGE:
		{
			//check if edge selected
			selection.type = EDGE;
			selection.selectedEdge = getEdgeSelection(*source, rayOrigin, rayDirection);
			break;
		}
		case FACE:
		{
			//check if face selected
			selection.type = FACE;
			selection.selectedFace = getFaceSelection(*source, rayOrigin, rayDirection, false);
			break;
		}
	}

	// store the selection as a class attribute
	this->selectionHover = &selection;
}

/**
 *	Finds and returns the vertex (from the specified mesh) that is
 *	within a threshold region of the implicit ray defined by the
 *	specified world point and direction vector.
 */
VertexHandle QGLSurfacesRenderer::getVertexSelection(const PolyMesh& mesh, 
													 const Vec3d& worldPoint, 
													 const Vec3d& rayDirection)
{
	const unsigned x = 0, y = 1, z = 2; // used to access Vec3d elements
	const unsigned MAX_HITS = 500;

	double						a, b, c;					// polynomial coefficients
	double						discriminant;
	double						root1, root2;
	double						smallestRoot;				// smallest root of all hits
	int							smallestRootIndex;			
	int							intersectCount;				// index of last vertex-handle stored
	double						rayFactors[MAX_HITS];		// corresponds to intersections[]
	VertexHandle				intersections[MAX_HITS];	// storage of handles for hit vertices
	FaceHandle					intersectedFace;			// the closest intersected face
	Vec3d						rayOrigin;
	Vec3d						sphereCenter;
	
	PolyMesh::ConstFaceVertexIter fvIterator;

	// get closest intersecting face
	intersectedFace = getFaceSelection(mesh, worldPoint, rayDirection, true);
	if (intersectedFace == PolyConnectivity::InvalidFaceHandle)
	{
		return PolyConnectivity::InvalidVertexHandle;
	}

	// calculating a (of ax^2 + bx + c)
	a = pow(rayDirection[x], 2.0) +
		pow(rayDirection[y], 2.0) +
		pow(rayDirection[z], 2.0);
	
	// checking vertices of closest face for intersection
	intersectCount  = 0;
	fvIterator = mesh.cfv_iter(intersectedFace);
	do
	{
		sphereCenter	= mesh.point(fvIterator);
		rayOrigin		= worldPoint - sphereCenter;	// pretending sphere is about origin

		// calculating b (of ax^2 + bx + c)
		b = 2 * (rayOrigin[x] * rayDirection[x] + 
			     rayOrigin[y] * rayDirection[y] + 
				 rayOrigin[z] * rayDirection[z]);

		// calculating c (of ax^2 + bx + c)
		c = pow(rayOrigin[x], 2.0) +
			pow(rayOrigin[y], 2.0) +
			pow(rayOrigin[z], 2.0) -
			pow(VERT_SELECT_THRESHOLD, 2.0);

		// negative discriminant? no intersection
		discriminant = b * b - 4 * a * c;
		if (discriminant >= 0)
		{
			// get the real roots of ax^2 + bx + c
			root1 = (-b + sqrt(discriminant))/(2 * a);
			root2 = (-b - sqrt(discriminant))/(2 * a);
			
			// store only the smallest non-negative root
			if (root1 >= 0  || root2 >= 0)
			{
				if (intersectCount < MAX_HITS)
				{
					intersections[intersectCount] = fvIterator.handle();

					if (root1 < 0)
					{
						rayFactors[intersectCount] = root2;
					}
					else if (root2 < 0)
					{
						rayFactors[intersectCount] = root1;
					}
					else if (root1 < root2)
					{
						rayFactors[intersectCount] = root1;
					}
					else if (root2 < root1)
					{
						rayFactors[intersectCount] = root2;
					}

					intersectCount++;
				}
			}
		}
	}
	while (++fvIterator == true);

	if (intersectCount > 0)
	{
		// find/return intersection with smallest root
		smallestRoot		= 0;
		smallestRootIndex	= 0;
		for (int i = 0; i < intersectCount; i++)
		{
			if ( i == 0  ||  rayFactors[i] < smallestRoot)
			{
				smallestRootIndex	= i;
				smallestRoot		= rayFactors[i];
			}
		}

		return intersections[smallestRootIndex];
	}

	return PolyConnectivity::InvalidVertexHandle;
}


/**
 *	Finds and returns the edge (from the specified mesh) that is
 *	within a threshold region of the implicit ray defined by the
 *	specified world point and direction vector.
 */
EdgeHandle QGLSurfacesRenderer::getEdgeSelection(const PolyMesh& mesh, 
												 const Vec3d& worldPoint, 
												 const Vec3d& rayDirection)
{
	const unsigned MAX_HITS = 500;

	bool			isEdgeHit;
	int				intersectCount;
	int				minCoefficientIndex;	// index of smallest ray to intersect
	double			selectableDistance;		// distance around edge to select
	double			minCoefficient;			// smallest ray to intersect
	double			rayCoefficient;			// length of ray at intersection
	double			rayFactors[MAX_HITS];
	EdgeHandle		intersects[MAX_HITS];
	Vec3d			edgePoint1;
	Vec3d			edgePoint2;
	Vec3d			normal;					// normal to worldpoint-edge triangle
	HalfedgeHandle	halfEdge;				// the current half-edge as we iterate
	FaceHandle		intersectedFace;
	vector<Vec3d>	edgePolygon;

	PolyMesh::ConstFaceEdgeIter eIterator;

	// getting the closest intersected face
	intersectedFace = getFaceSelection(mesh, worldPoint, rayDirection, true);
	if (intersectedFace == PolyConnectivity::InvalidFaceHandle)
	{
		return PolyConnectivity::InvalidEdgeHandle;
	}

	// if subdivisions are high, polygons are small
	// we need to decrease selectable area surrounding
	// each edge
	selectableDistance = EDGE_SELECT_THRESHOLD;
	if (this->modelFrame->numSubdivs > 2)
	{
		selectableDistance /= (this->modelFrame->numSubdivs - 1);
	}

	// checking edges of closest face for intersection
	intersectCount = 0;
	eIterator = mesh.cfe_iter(intersectedFace);
	do
	{
		isEdgeHit = false; // assume no intersection

		// find the normal used to create the edge-polygon
		halfEdge	= mesh.halfedge_handle(eIterator.handle(), 0);
		edgePoint1	= mesh.point(mesh.to_vertex_handle(halfEdge));
		edgePoint2	= mesh.point(mesh.from_vertex_handle(halfEdge));
		normal		= cross(edgePoint1 - worldPoint, 
			                edgePoint2 - worldPoint).normalize();

		// create the edge-polygon using normal as an offset
		edgePolygon.clear();
		edgePolygon.push_back(edgePoint1 + (normal * selectableDistance));
		edgePolygon.push_back(edgePoint2 + (normal * selectableDistance));
		edgePolygon.push_back(edgePoint2 - (normal * selectableDistance));
		edgePolygon.push_back(edgePoint1 - (normal * selectableDistance));

		// test if the ray intersects the edge-polygon
		isEdgeHit = isPolygonHit(worldPoint, rayDirection, 
			                     edgePolygon, rayCoefficient);
		
		// woohoo! an intersection
		if (isEdgeHit)
		{
			if (intersectCount < MAX_HITS)
			{
				intersects[intersectCount] = eIterator.handle();
				rayFactors[intersectCount] = rayCoefficient;
				intersectCount++;
			}
		}

	}
	while (++eIterator == true);

	if (intersectCount > 0)
	{
		// find/return the intersection with smallest ray
		minCoefficient		= 0;
		minCoefficientIndex = 0;
		for (int i = 0; i < intersectCount; i++)
		{
			if (i == 0  ||  rayFactors[i] < minCoefficient)
			{
				minCoefficient		= rayFactors[i];
				minCoefficientIndex = i;
			}
		}

		return intersects[minCoefficientIndex];
	}

	return PolyConnectivity::InvalidEdgeHandle;
}


/**
 *	Finds and returns the face (from the specified mesh) that is
 *	intersected by the implicit ray defined by the specified world 
 *	point and direction vector.
 */
FaceHandle QGLSurfacesRenderer::getFaceSelection(const PolyMesh& mesh, 
												 const Vec3d& worldPoint, 
												 const Vec3d& rayDirection,
												 bool stretchFaces)
{
	const unsigned MAX_HITS = 500;

	bool			isPolyHit;
	int				intersectCount;
	int				minCoefficientIndex;		// index of smallest rayCoefficient
	double			stretchFactor;				// how much faces get stretched
	double			minCoefficient;				// smallest rayCoefficient value
	double			rayCoefficient;				// length of ray at intersection
	double			rayFactors[MAX_HITS];		// corresponds to intersections[]
	FaceHandle		intersections[MAX_HITS];	// all intersected faces
	Vec3d			faceVertex;					// just a temp variable
	Vec3d			faceCentroid;
	vector<Vec3d>	faceVertices;

	PolyMesh::Point					tempPoint;
	PolyMesh::ConstFaceIter			fIterator(mesh.faces_begin());
	PolyMesh::ConstFaceVertexIter	fvIterator;

	// set stretch factor for faces (if applicable)
	if (stretchFaces)
	{
		stretchFactor = FACE_STRETCH_FACTOR;
		if (this->modelFrame->numSubdivs > 2)
		{
			stretchFactor /= (this->modelFrame->numSubdivs - 1);
		}
	}

	intersectCount = 0; // no confirmed face intersections yet

	do
	{
		isPolyHit = false; // assume no intersection

		// get all vertices from the current face
		faceVertices.clear();
		fvIterator = mesh.cfv_iter(fIterator.handle());
		do 
		{	
			faceVertex = mesh.point(fvIterator);
			if (stretchFaces)
			{
				// stretch vertex away from centroid
				mesh.calc_face_centroid(fIterator.handle(), tempPoint);
				faceCentroid = Vec3d(tempPoint);
				faceVertex = faceCentroid * (-stretchFactor) + 
					         faceVertex * (1+stretchFactor);
			}
			faceVertices.push_back(faceVertex); 
		}
		while(++fvIterator != false);

		// perform intersection test using the face points
		isPolyHit = isPolygonHit(worldPoint, rayDirection, 
								 faceVertices, rayCoefficient);

		// hooray! we hit the face
		if (isPolyHit)
		{
			if (intersectCount < MAX_HITS)
			{
				intersections[intersectCount] = fIterator.handle();
				rayFactors[intersectCount] = rayCoefficient;
				intersectCount++;
			}
		}
	}
	while (++fIterator != mesh.faces_end());

	if (intersectCount > 0)
	{
		// find/return intersection with smallest ray
		minCoefficient		= 0;
		minCoefficientIndex	= 0;
		for (int i = 0; i < intersectCount; i++)
		{
			if (i == 0  ||  rayFactors[i] < minCoefficient)
			{
				minCoefficient		= rayFactors[i];
				minCoefficientIndex	= i;
			}
		}

		return intersections[minCoefficientIndex];
	}

	return PolyConnectivity::InvalidFaceHandle;
}


//==============================================================================
// Utility Methods
//==============================================================================

/**	### TODO Add method description 
 */
void QGLSurfacesRenderer::unprojectRay(const Vec3d& windowPoint, 
									   Vec3d& rayOrigin, 
									   Vec3d& rayDirection)
{
	rayOrigin = windowPoint;
	unprojectPoint(rayOrigin, 0.0);

	rayDirection = windowPoint;
	unprojectPoint(rayDirection, 1.0);
	rayDirection -= rayOrigin;
	rayDirection.normalize();
}


/**	Determines whether the specified test point is inside (returns true) or
 *	outside (returns false) the specified closed 2D curve. To ensure a 2D
 *	test, the ignoredAxis specified is ignored (0 = x, 1 = y, 2 = z).
 */
bool QGLSurfacesRenderer::isInsideCurve(const Vec3d& testPoint, 
										const vector<Vec3d> polygon, 
										int ignoredAxis)
{
	const unsigned x = 0, y = 1, z = 2;

	int				i, j;				// counter variables
	int				intersectCount;
	int				p1Index;			// index of first point in a line
	int				p2Index;		    // index of second point in a line
	int				axis1, axis2;		// the two non-ignored axis
	double			rayCoefficient;		// place on ray where line intersects
	double			lineCoefficient;	// place on line where ray intersects
	double			fullLineLength;		// length of line between two points
	double			minLineX;			// smallest x value of a line
	double			minLineY;			// smallest y value of a line
	Vec3d			rayDirection;
	Vec3d			rayOrigin;
	vector<Vec3d>	polyPoints;

	// first lets "un-const" the const parameters
	polyPoints = polygon;
	rayOrigin = testPoint;	

	// create a random ray from the test point and assign
	// the two non-ignored axis
	axis1 = -1;
	axis2 = -1;
	for (j = x; j <= z; j++)
	{
		if (j != ignoredAxis)
		{
			rayDirection[j] = 1;  // any value will do here

			if (axis1 == -1)		{ axis1 = j; }
			else if (axis2 == -1)	{ axis2 = j; }
		}
		else
		{
			rayDirection[j] = 0;
		}
	}
	rayDirection.normalize();

	// shortening some variable names (you'll see why)
	Vec3d	Vr = rayDirection;
	double&	LC = lineCoefficient;

	// check for intersection with every edge in the polygon
	intersectCount = 0;
	for (i = 0; i < polyPoints.size(); i++)
	{
		p1Index = i;
		p2Index = (i+1) % polyPoints.size(); // wraps around end of vector

		// again, just shortening variable names
		Vec3d P0 = polyPoints[p1Index];
		Vec3d P1 = polyPoints[p2Index];
		Vec3d Pr = rayOrigin;

		// shifting the line to the +/+ quadrant so that the
		// upcoming denominator can never be zero
		minLineX = min(P0[axis1], P1[axis1]);
		minLineY = min(P0[axis2], P1[axis2]);
		if (minLineX < 0)
		{
			P0[axis1] += -minLineX;
			P1[axis1] += -minLineX;
			Pr[axis1] += -minLineX;
		}
		if (minLineY < 0)
		{
			P0[axis2] += -minLineY;
			P1[axis2] += -minLineY;
			Pr[axis2] += -minLineY;
		}
		if (P0[axis1] - P1[axis1] == P0[axis2] - P1[axis2])
		{
			P0[axis2] += 1;
			P1[axis2] += 1;
			Pr[axis2] += 1;
		}

		// calculating the line coefficient
		lineCoefficient = (Vr[axis1]*(P1[axis2] - Pr[axis2]) - Vr[axis2]*(P1[axis1] - Pr[axis1])) /
			              (Vr[axis2]*(P0[axis1] - P1[axis1]) - Vr[axis1]*(P0[axis2] - P1[axis2]));

		// calculating the ray coefficient
		// (LC is lineCoefficient)
		rayCoefficient = (LC*P0[axis1] + (1 - LC)*P1[axis1] - Pr[axis1]) / Vr[axis1];

		// check if intersection conditions are satisfied
		if (rayCoefficient >= 0  &&  
			lineCoefficient > 0  &&
			lineCoefficient <= 1)
		{
			intersectCount++;  // woah! an intersection
		}
	}

	return (intersectCount % 2 != 0);  // odd => true, even => false
}


/**	Checks if the specified polygon is intersected by a ray. The
 *	ray is defined by the specified parameters. Value of coefficient
 *	is modified to store the length of the ray at intersection.
 */
bool QGLSurfacesRenderer::isPolygonHit(const Vec3d& rayOrigin, 
									   const Vec3d& rayDirection, 
									   const std::vector<Vec3d>& polygon,
									   double& coefficient)
{
	const unsigned x = 0, y = 1, z = 2; // used to access Vec3d elements
	const unsigned MAX_HITS = 50;

	bool			isPolyHit;
	int				i, j, k, l;					// counter variables
	int				intersectCount;				
	unsigned		leastExtentAxis;			// affects vertices the least amount
	double			minCompValue;				// min of all x, y, or z in set of vectors
	double			maxCompValue;				// max of all x, y, or z in set of vectors
	double			minVariance;				// least distance between x, y, or z
	double			rayCoefficient;				// length of ray at intersection
	double			denominator;				// denominator of intersect equation
	Vec3d			facePoints[3];				// used for cross-product
	Vec3d			planeNormal;
	Vec3d			planePoint;
	Vec3d			planeHitPoint;				// point of ray intersection with plane
	Vec3d			faceCentroid;				
	Vec3d			projectedPoint;				// a face point, less one axis
	FaceHandle		intersections[MAX_HITS];	// all intersected faces
	vector<Vec3d>	projectedPoints;

	// PART 1: Detect plane intersection:

	// gather points for a cross-product
	for (i = 0; i < 3; i++)
	{
		facePoints[i] = polygon.at(i);
	}
	
	// calculate normal for the face's plane
	planeNormal = facePoints[1] - facePoints[0];
	planeNormal = cross(planeNormal, facePoints[2] - facePoints[0]);

	// taking precaution against zero denominator
	denominator = dot(planeNormal, rayDirection);
	if (denominator != 0)
	{
		planePoint = facePoints[0]; // random choice

		// calculate ray coefficient at intersection
		rayCoefficient = dot(planeNormal, planePoint - rayOrigin) / denominator;

		// hooray! a plane intersection
		if (rayCoefficient >= 0) 
		{
			// PART 2: Check if intersection point is in polygon:

			// get the axis of least extent
			minVariance = 0;
			for (j = x; j <= z; j++)
			{
				// find min variance on current axis
				minCompValue = polygon.at(0)[j];
				maxCompValue = polygon.at(0)[j];
				for (k = 0; k < polygon.size(); k++)
				{
					minCompValue = min(minCompValue, polygon.at(k)[j]);
					maxCompValue = max(maxCompValue, polygon.at(k)[j]);
				}

				// compare to current min variance
				if (j == x  ||  (maxCompValue - minCompValue) < minVariance)
				{
					minVariance		 = maxCompValue - minCompValue;
					leastExtentAxis	 = j;
				}
			}
			
			// flatten face vertices along axis of least extent
			// store result in a temporary location
			for (k = 0; k < polygon.size(); k++)
			{
				projectedPoint = polygon.at(k);
				projectedPoint[leastExtentAxis] = 0;
				projectedPoints.push_back(projectedPoint);
			}
			
			// test if the plane intersection point is in the polygon
			isPolyHit	  = false;
			planeHitPoint = rayOrigin + rayDirection * rayCoefficient;
			isPolyHit	  = isInsideCurve(planeHitPoint, projectedPoints, leastExtentAxis);
			if (isPolyHit)
			{
				// hooray! we hit the polygon
				coefficient = rayCoefficient;
				return true;
			}
		}
	}

	return false;
}


/**	Unprojects the specified point from the window to a viewing plane
 *	that is some distance away from the near plane.
 */
void QGLSurfacesRenderer::unprojectPoint(Vec3d& windowPoint, 
										 const double distance)
{
	const unsigned x = 0, y = 1, z = 2;

	double   pX, pY, pZ;
	GLint    vp[4];  
	GLdouble mvmatrix[16];
	GLdouble projmatrix[16];

	// Get viewport info: [x, y, width, height]
	glGetIntegerv(GL_VIEWPORT, vp);
	
	// Get the modelview and projection matrices
	glGetDoublev(GL_MODELVIEW_MATRIX, mvmatrix);
	glGetDoublev(GL_PROJECTION_MATRIX, projmatrix);
	
	// unprojecting onto the near plane
	gluUnProject(windowPoint[x], vp[3] - windowPoint[y], distance, 
		         mvmatrix, projmatrix, vp, 
				 &pX, &pY, &pZ);

	windowPoint[x] = pX;
	windowPoint[y] = pY;
	windowPoint[z] = pZ;
}


/**	Projects a world point into window coordinates.
 */
void QGLSurfacesRenderer::projectPoint(Vec3d& worldPoint)
{
	const unsigned x = 0, y = 1, z = 2;

	double   pX, pY, pZ;
	GLint    vp[4];  
	GLdouble mvmatrix[16];
	GLdouble projmatrix[16];

	// Get viewport info: [x, y, width, height]
	glGetIntegerv(GL_VIEWPORT, vp);
	
	// Get the modelview and projection matrices
	glGetDoublev(GL_MODELVIEW_MATRIX, mvmatrix);
	glGetDoublev(GL_PROJECTION_MATRIX, projmatrix);
	
	// unprojecting onto the near plane
	gluProject(worldPoint[x], worldPoint[y], worldPoint[z], 
		       mvmatrix, projmatrix, vp, 
			   &pX, &pY, &pZ);

	worldPoint[x] = pX;
	worldPoint[y] = vp[3] - pY;
	worldPoint[z] = pZ;
}


/**	Returns a sphere that bounds all points of the specified
 *	source mesh.
 */
Sphere& QGLSurfacesRenderer::getBoundsSphere(const PolyMesh& source)
{
	const unsigned x = 0, y = 1, z = 2;

	int		i, j;				// counter variables
	int		axis;				// addresses an axis (x, y, or z)
	double	min[3];				// smallest value on each axis
	double	max[3];				// largest value on each axis
	double	radiusSquared;		
	double	edgeToPoint;		// distance: sphere edge to currentPoint
	double	centerToPoint;		// distance: sphere center to currentPoint
	double	centerToPointSq;	// centerToVertex squared
	double	maxDistance;		// maximum distance between points
	double	distance;			// in loop: distance between two points
	Vec3d	farthestPoints[2];	// the two source vertices farthest apart
	Vec3d	minPoint[3];		// for each axis: smallest point on it
	Vec3d	maxPoint[3];		// for each axis: largest point on it
	Vec3d	currentPoint;		// in loop: current source vertex
	Vec3d	backPoint;			// point on reverse side of sphere
	Vec3d	maxDistanceVector;	// max displacement between source vertices
	Vec3d	distanceVector;		// in loop: displacement between points
	Sphere* result;

	PolyMesh::ConstVertexIter vIterator; //vertex iterator

	result = new Sphere();

	//PART 1: Find two points furthest from each other

	// find extrema points all axis (x, y, and z)
	for (axis = x; axis <= z; axis++)
	{
		vIterator      = source.vertices_begin();
		currentPoint   = source.point(vIterator.handle());
		minPoint[axis] = currentPoint;
		maxPoint[axis] = currentPoint;
		min[axis]      = currentPoint[axis];
		max[axis]      = currentPoint[axis];
		do	
		{
			currentPoint = source.point(vIterator.handle());
			
			// minimum comparison
			if (currentPoint[axis] < min[axis])
			{
				minPoint[axis] = currentPoint;
				min[axis]      = currentPoint[axis];
			}

			// maximum comparison
			if (currentPoint[axis] > max[axis])
			{
				maxPoint[axis] = currentPoint;
				max[axis]      = currentPoint[axis];
			}
		}
		while (++vIterator != source.vertices_end());
	}

	// find the two extrema furthest from eachother
	distanceVector = Vec3d(0,0,0);
	for (i = x; i <= z; i++)
	{
		// compare min to all max
		for (j = x; j <= z; j++)
		{
			distanceVector	= minPoint[i] - maxPoint[j];
			distance		= dot(distanceVector, distanceVector);

			if (j == 0  || distance > maxDistance)
			{
				maxDistance	      = distance;
				farthestPoints[0] = minPoint[i];
				farthestPoints[1] = maxPoint[j];
			}
		}

		// compare min to other min
		for (j = i+1; j <= z; j++)
		{
			distanceVector	= minPoint[i] - minPoint[j];
			distance		= dot(distanceVector, distanceVector);

			if (distance > maxDistance)
			{
				maxDistance	      = distance;
				farthestPoints[0] = minPoint[i];
				farthestPoints[1] = minPoint[j];
			}
		}
	}
	result->center    = farthestPoints[1] * 0.5 + farthestPoints[0] * 0.5;
	result->radius    = sqrt(maxDistance) / 2;

	//PART 2: Gradually expand sphere to cover all points

	vIterator = source.vertices_begin();
	do
	{
		radiusSquared   = pow(result->radius, 2);
		currentPoint    = source.point(vIterator.handle());
		distanceVector  = currentPoint - result->center;
		centerToPointSq = dot(distanceVector, distanceVector);

		if (centerToPointSq > radiusSquared)
		{
			centerToPoint  = sqrt(centerToPointSq);
			result->radius = (result->radius + centerToPoint) / 2;
			radiusSquared  = pow(result->radius, 2);
			edgeToPoint    = centerToPoint - result->radius;
			result->center = (result->center * result->radius + 
				              currentPoint * edgeToPoint) * 1/centerToPoint;

		}
		
	}
	while (++vIterator != source.vertices_end());

	return *result;
}


/**	Zooms into the specified mesh as tightly as possible.
 */
void QGLSurfacesRenderer::zoomToMesh(const PolyMesh& target)
{
	const unsigned	x = 0, y = 1, z = 2;

	double	centerRayLength;
	double	newRadius;				// desired sphere radius (window coords)
	double	oldRadius;				// old sphere radius (window coords)
	double	radiusRatio;			// ratio, old radius to desired radius
	Vec3d	sideDirection;			
	Vec3d	sphereEdgePoint;
	Vec3d	zoomDisplace;
	Vec3d	windowCenter;			// window center in window coordinates
	Vec3d	centerRayOrigin;		// center of the near viewing plane
	Vec3d	centerRayDirection;		// direction normal to near plane
	Vec3d	directionToSphere;		// from near plane center to sphere center
	Vec3d	newSphereCenter;		// a point along the center ray
	Vec3d	sphereOffset;			// offset to move sphere to center ray
	Vec3d	newDiameterP1;			// point one of the new sphere diameter
	Vec3d	newDiameterP2;			// point two of the new sphere diameter

	Sphere& boundsSphere = getBoundsSphere(target);
	
	//PART 1: Centering the target on screen

	// create a ray centered on the near plane
	windowCenter = Vec3d(width() / 2, height() / 2, 0);
	unprojectRay(windowCenter, centerRayOrigin, centerRayDirection);
	
	// calculate length of center ray
	directionToSphere = bounds.center - centerRayOrigin;
	centerRayLength   = dot(centerRayDirection, directionToSphere);
		           
	// offset scene so that target is on center ray
	newSphereCenter = centerRayOrigin + centerRayDirection * centerRayLength;
		
	oldBounds = bounds;
	oldBounds.center = newSphereCenter;

	bounds = boundsSphere;
}

void QGLSurfacesRenderer::animateZoom()
{
	double interpFrom, interpTo;

	interpTo	= 1.0 - ZOOM_STEP_SIZE;
	interpFrom	= ZOOM_STEP_SIZE;

	// interpolate new sphere center
	interpBounds.center = (interpTo - zoomInterpVal)*oldBounds.center + 
						  (interpFrom + zoomInterpVal)*bounds.center;

	// interpolate new sphere radius
	interpBounds.radius = (interpTo - zoomInterpVal)*oldBounds.radius + 
						  (interpFrom + zoomInterpVal)*bounds.radius;

	// center scene on interpolated sphere
	modelFrame->t.setScenePosition(Vec3f(interpBounds.center), interpBounds.radius);
	
	// check zoom termination condition
	if (zoomInterpVal == interpTo)
	{
		stopZoomAnimation();
		this->zooming = false;

		updateGL();
		return;
	}

	zoomInterpVal += ZOOM_STEP_SIZE;

	// if next zoom step > intended value...
	if(zoomInterpVal > interpTo)
	{
		// ... set interpolation to intended value
		zoomInterpVal = interpTo;
	}

	updateGL();
}

string QGLSurfacesRenderer::weightToFraction(double theWeight)
{
	int storedI, storedJ;
	stringstream s1, s2, s3;
	string rtn1, rtn2;

	for(int i = 1; i < 100; i++)
	{
		for(int j = 1; j <=i; j++)
		{
			double temp = (double)j/i;
			if(temp == theWeight)
			{
				string rtn1 = "";
				s1 << j;
				s1 >> rtn1;
				rtn2 = "";
				s2 << i;
				s2 >> rtn2;
				return rtn1 + " / " + rtn2;
			}
		}
	}

	string other ="";
	s3 << theWeight;
	s3 >> other;
	other = other.substr(0,5);
	return other;
}